package websocket

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"math/rand"
	"net/http"
	"peilian-api/app/global/databases/tables"
	"peilian-api/app/global/variable"
	"peilian-api/app/model"
	"peilian-api/app/services/report_service"
	"peilian-api/utils/tools"
	"strings"
	"time"
)

type Recommend struct {
	*Client
	IRecommend
	keyWords      []string
	questions     []string
	answers       []string
	analyzeLabels []string
	generalLabels []string
	client        Client
	evaluateResp  *model.LabelEvaluate
}

func (r *Recommend) initAttr() {
	if r.questions == nil {
		r.questions = make([]string, 0)
	}
	if r.answers == nil {
		r.answers = make([]string, 0)
	}
	if r.analyzeLabels == nil {
		r.analyzeLabels = make([]string, 0)
	}
	if r.generalLabels == nil {
		r.generalLabels = make([]string, 0)
	}
}

func (r *Recommend) getFinishedStatus() int {
	return tables.IoStatus
}

func (r *Recommend) FirstQuesVideo() []byte {
	defaultClient := DefaultClient{r.Client}
	return defaultClient.FirstQuesVideo()
}

func (r *Recommend) NextQuestion(flag int) []byte {
	tag := r.Bus.CurrentTag + 1
	var nextQues tables.RealQues
A:
	if err := variable.DB.MySQL.Where("session_id=? and current_tag=?", r.Bus.Session.ID, tag).
		Preload("Question").First(&nextQues).Error; err != nil {
		// 下面为兜底方案
		questionTypeOrder := r.getQuesTypeOrder()
		postQuesType, _ := questionTypeOrder[r.Bus.CurrentTag+1]
		switch postQuesType {
		case "经验类":
			// 原逻辑为专业类问题加通用类问题，无经验类问题
			qs := make([]*tables.Question, 0)
			if err := variable.DB.MySQL.Where("career_id=?", tables.Experiencing).
				Find(&qs).Error; err == nil && len(qs) > 0 {
				randSub := rand.Intn(len(qs))
				q := qs[randSub]
				_ = r.CacheRealQues(q)
				r.Logger.Debug(fmt.Sprintf("[recommend]第[%d]道题, 经验类问题兜底", r.Bus.CurrentTag+1))
				goto A // 缓存成功后重新获取下一题
			}
			fallthrough
		default:
			// 专业类 通用类走原出题逻辑
			defaultClient := DefaultClient{r.Client}
			r.Logger.Debug(fmt.Sprintf("[recommend]第[%d]道题, 旧版出题逻辑", r.Bus.CurrentTag+1))
			return defaultClient.NextQuestion(flag)
		}
	}
	r.Bus.CurrentTag += 1
	r.Bus.CurrentQuestion = &nextQues.Question
	return r.ResClient("ask", &VideoInfo{
		Video:          nextQues.Question.Video,
		PcVideo:        nextQues.Question.PcVideo,
		Text:           nextQues.Question.Text,
		QuestionOrigin: nextQues.Question.QuestionOrigin,
	})
}

func (r *Recommend) EvaluatePartLabels() ([]string, error) {
	if r.keyWords == nil {
		r.keyWords = make([]string, 0)
	}
	if err := json.Unmarshal([]byte(r.client.Bus.CurrentQuestion.KeyWords), &r.analyzeLabels); err != nil {
		return nil, err
	}
	r.keyWords = append(r.keyWords, r.analyzeLabels...)
	reqStruct := map[string]interface{}{
		"job_name":           r.client.Bus.Session.Career.Name,
		"label_extract_list": r.partCacheLabels,
		"analyze_labels":     r.keyWords,
	}
	reqData, _ := json.Marshal(reqStruct)
	r.Logger.Debug("[recommend]标签评测的请求参数: ", string(reqData))
	r.evaluateResp = report_service.FetchEvaluationScore(reqData)
	respData, _ := json.Marshal(r.evaluateResp)
	r.Logger.Debug("[recommend]标签评测的结果: ", string(respData))
	posProLabels := make([]string, 0)
	for _, pos := range r.evaluateResp.PosWordScore.Profession {
		if pos[1].(float64) > 0 {
			posProLabels = append(posProLabels, pos[0].(string))
		}
	}
	for _, pos := range r.evaluateResp.NegScoreDict.Profession {
		if pos[1].(float64) > 0 {
			posProLabels = append(posProLabels, pos[0].(string))
		}
	}
	r.Logger.Debug("[recommend]标签评测得到的优势标签: ", strings.Join(posProLabels, "|"))
	return posProLabels, nil
}

func (r *Recommend) HttpClient(req interface{}, url string) (map[string]interface{}, error) {
	reqJsonData, _ := json.Marshal(req)
	r.Logger.Debug(fmt.Sprintf("[recommend]请求[%s]参数: ", url), string(reqJsonData))
	request, err := http.NewRequest(
		"POST",
		url,
		bytes.NewReader(reqJsonData),
	)
	if err != nil {
		return nil, err
	}
	request.Header.Set("Content-Type", "application/json")
	client := &http.Client{
		Timeout: 10 * time.Second,
	}
	resp, err := client.Do(request)
	if err != nil {
		r.Logger.ErrorF(r.Ctx, "[recommend]请求失败: "+err.Error())
		return nil, err
	}
	if resp != nil {
		defer resp.Body.Close()
	}

	res := make(map[string]interface{})
	err = tools.ReadResponseBody(resp, &res)
	if err != nil {
		errMsg := "[recommend]接口响应结果解析失败" + err.Error()
		r.Logger.ErrorF(r.Ctx, errMsg)
		return nil, err
	}
	if code, _ := res["code"].(int64); code != 0 {
		errMsg := "[recommend]接口响应结果解析失败" + err.Error()
		r.Logger.ErrorF(r.Ctx, errMsg)
		return nil, errors.New(errMsg)
	}
	return res, nil
}

func (r *Recommend) getRecommendNextQuestion(req *RecommendNextReq) (string, error) {
	res, err := r.HttpClient(req, variable.Config.Services.AlgRecommendNextQuestion)
	if err != nil {
		return "", err
	}
	nextQuesText := res["data"].(string)
	if nextQuesText == "wrong" {
		return "", errors.New("推荐接口返回wrong")
	}
	return nextQuesText, nil
}

func (r *Recommend) StarRule(preAns string) (string, error) {
	req := map[string]string{"pre_ans": preAns}
	res, err := r.HttpClient(req, variable.Config.Services.AlgRecommendFollowingAsk)
	if err != nil {
		return "", err
	}
	starExist := res["data"].(map[string]interface{})
	starLabel0 := make([]string, 0)
	for k, v := range starExist {
		if strings.HasSuffix(v.(string), "0") {
			starLabel0 = append(starLabel0, k)
		}
	}
	isIn := func(star string) bool {
		for _, l := range starLabel0 {
			if star == l {
				return true
			}
		}
		return false
	}
	switch {
	case isIn("A"):
		return "a", nil
	case isIn("R"):
		return "r", nil
	case isIn("S") && isIn("T"):
		return "a", nil
	default:
		return "st", nil
	}
}

func (r *Recommend) CacheStarQuestion(starType string) (err error) {
	starQuestions := make([]*tables.Question, 0)
	err = variable.DB.MySQL.Select("ID", "Text").
		Where("career_id=? and tag=?", tables.Star, "star_"+starType).
		Find(&starQuestions).Error
	if err != nil {
		return
	}
	randSub := rand.Intn(len(starQuestions))
	q := starQuestions[randSub]
	return r.CacheRealQues(q)
}

func (r *Recommend) CacheRealQues(q *tables.Question) (err error) {

	var realQues tables.RealQues
	if err = variable.DB.MySQL.Where(&tables.RealQues{
		QuestionID: q.ID,
		SessionID:  r.Bus.Session.ID,
	}).Attrs(tables.RealQues{
		QuestionType:   0, // 0-正常问题, 1-追问
		QuestionText:   q.Text,
		CurrentTag:     uint(r.Client.Bus.CurrentTag + 1),
		BelongToID:     nil, //追问问题的父级问题ID
		NextType:       "recommend",
		QuestionOrigin: q.QuestionOrigin,
		AnsAnalysisTag: q.AnsAnalysisTag,
	}).FirstOrCreate(&realQues).Error; err != nil {
		r.Logger.Warn(err.Error())
		return
	}
	r.Logger.InfoF(r.Ctx, "========================================================")
	r.Logger.InfoF(r.Ctx, fmt.Sprintf("[第%d题]推荐题目缓存完毕: ", r.client.Bus.CurrentTag+1), q.Text)
	r.Logger.InfoF(r.Ctx, "========================================================")
	return nil
}

func (r *Recommend) CacheComplementQuestion() (err error) {
	complementQuestions := make([]*tables.Question, 0)
	if err = variable.DB.MySQL.Select("ID", "Text").
		Where("career_id=?", tables.Complement).
		Find(&complementQuestions).Error; err != nil {
		return
	}
	randSub := rand.Intn(len(complementQuestions))
	q := complementQuestions[randSub]
	return r.CacheRealQues(q)
}

func (r *Recommend) CacheNextQues(client Client) error {
	r.initAttr()
	r.client = client
	r.questions = append(r.questions, client.Bus.CurrentQuestion.Text)
	r.answers = append(r.answers, client.Bus.CombineText)

	// 获取下一道题
	postNeedLabel := make([]string, 0)
	questionTypeOrder := r.getQuesTypeOrder()
	postQuesType, ok := questionTypeOrder[client.Bus.CurrentTag+1]
	if !ok {
		return errors.New("超出题目限制")
	}
	r.Logger.Debug(fmt.Sprintf("[recommend]下一道题目[第%d题]类型: ", r.client.Bus.CurrentTag+1), postQuesType)
	switch postQuesType {
	case "专业类":
		// 获取题目回答评测评估所得的标签词
		posProLabels, err := r.EvaluatePartLabels()
		if err != nil {
			return err
		}
		switch {
		case client.Bus.CurrentTag != 1:
			// 非自我介绍
			diffLabels := tools.DiffArray(r.analyzeLabels, postNeedLabel)
			if len(diffLabels) != 0 {
				postNeedLabel = append(postNeedLabel, diffLabels[0])
			}
		case len(posProLabels) != 0:
			randSub := rand.Intn(len(posProLabels))
			postNeedLabel = append(postNeedLabel, posProLabels[randSub])
		}
		postQuesType = client.Bus.Session.Career.MongoRangeId
	case "经验类":
		// 获取题目回答评测评估所得的标签词
		_, err := r.EvaluatePartLabels()
		if err != nil {
			return err
		}
		for _, gen := range r.evaluateResp.PosWordScore.General {
			if gen[1].(float64) > 0 {
				postNeedLabel = append(postNeedLabel, gen[0].(string))
			}
		}
		var experienceCareer tables.Career
		if err := variable.DB.MySQL.First(&experienceCareer, tables.Experiencing).Error; err != nil {
			return err
		}
		postQuesType = experienceCareer.MongoRangeId
	case "star":
		starType, err := r.StarRule(client.Bus.CombineText)
		if err != nil {
			return err
		}
		return r.CacheStarQuestion(starType)
	case "通用类":
		posProLabels, err := r.EvaluatePartLabels()
		if err != nil {
			return err
		}
		diffLabels := tools.DiffArray(r.keyWords, posProLabels)
		if len(diffLabels) != 0 {
			postNeedLabel = append(postNeedLabel, diffLabels[0])
		}
		var commonCareer tables.Career
		if err := variable.DB.MySQL.First(&commonCareer, tables.QCommonId).Error; err != nil {
			return err
		}
		postQuesType = commonCareer.MongoRangeId
	case "补充":
		return r.CacheComplementQuestion()
	case "common":
		return nil
	default:
		return nil
	}
	req := &RecommendNextReq{
		PostQuestionType: postQuesType,
		Model:            "glm10b",
		PreHaveLabel:     make([]string, 0),
		PostNeedLabel:    postNeedLabel,
		Flag:             1,
		History: &History{
			Question: r.questions,
			Answer:   r.answers,
		},
	}
	r.Logger.Info(fmt.Sprintf("[recommend]开始请求算法推荐下一题[第%d题]...", client.Bus.CurrentTag+1))
	questionText, err := r.getRecommendNextQuestion(req)
	if err != nil {
		return err
	}
	r.Logger.Info(fmt.Sprintf("[recommend]算法推荐的下一题[第%d题]为: ", client.Bus.CurrentTag+1), questionText)
	// 缓存起来，以备返回给面试主流程
	if err := r.Cache(questionText); err != nil {
		return err
	}
	return nil
}

func (r *Recommend) Cache(questionText string) error {
	var (
		q        tables.Question
		careerId uint
	)
	questionTypeOrder := r.getQuesTypeOrder()
	switch questionTypeOrder[r.client.Bus.CurrentTag+1] {
	case "经验类":
		careerId = tables.Experiencing
	case "通用类":
		careerId = tables.QCommonId
	default:
		careerId = r.client.Bus.Session.CareerID
	}
	if err := variable.DB.MySQL.Where("text=? and career_id=?", questionText, careerId).
		First(&q).Error; err != nil {
		return err
	}
	return r.CacheRealQues(&q)
}

type RecommendNextReq struct {
	PostQuestionType string   `json:"post_question_type"`
	Model            string   `json:"model"`
	PreHaveLabel     []string `json:"pre_have_label"`
	PostNeedLabel    []string `json:"post_need_label"`
	Flag             int      `json:"flag"`
	History          *History `json:"history"`
}

type History struct {
	Question []string `json:"question"`
	Answer   []string `json:"answer"`
}

type OccupationCareer struct {
}

func (o *OccupationCareer) getTotal() int {
	return len(o.getQuesTypeOrder())
}

func (o *OccupationCareer) getQuesTypeOrder() map[int]string {
	return map[int]string{
		1: "自我介绍",
		2: "专业类",
		3: "专业类",
		4: "经验类",
		5: "star",
		6: "通用类",
		7: "补充",
	}
}

type CommonCareer struct {
}

func (c *CommonCareer) getTotal() int {
	return len(c.getQuesTypeOrder())
}

func (c *CommonCareer) getQuesTypeOrder() map[int]string {
	return map[int]string{
		1: "自我介绍",
		2: "common",
		3: "common",
		4: "common",
		5: "经验类",
		6: "star",
	}
}
